import { action } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
import { restartableTask, timeout } from 'ember-concurrency';
import { modifier } from 'ember-modifier';

export default class Tooltip extends Component {
  @tracked anchorElement = null;
  @tracked visible = false;

  get containerElement() {
    return document.querySelector('#tooltip-container');
  }

  @action show() {
    this.hideTask.cancelAll();
    this.visible = true;
  }

  @action hide() {
    this.hideTask.perform().catch(() => {
      // ignore cancelation errors
    });
  }

  hideTask = restartableTask(async () => {
    await timeout(100);
    this.visible = false;
  });

  onInsertAnchor = modifier((anchor, [component]) => {
    component.anchorElement = anchor.parentElement;

    let events = [
      ['mouseenter', component.show],
      ['mouseleave', component.hide],
      ['focus', component.show],
      ['blur', component.hide],
    ];

    for (let [event, listener] of events) {
      component.anchorElement.addEventListener(event, listener);
    }

    return () => {
      for (let [event, listener] of events) {
        component.anchorElement?.removeEventListener(event, listener);
      }
    };
  });

  attachTooltip = modifier((floatingElement, [component], { side = 'top' } = {}) => {
    let referenceElement = component.anchorElement;

    async function update() {
      let middleware = [offset(5), flip(), shift({ padding: 5 })];

      let { x, y } = await computePosition(referenceElement, floatingElement, {
        placement: side,
        middleware,
      });

      Object.assign(floatingElement.style, {
        left: `${x}px`,
        top: `${y}px`,
      });
    }

    let cleanup = autoUpdate(referenceElement, floatingElement, update);

    floatingElement.addEventListener('mouseenter', component.show);
    floatingElement.addEventListener('mouseleave', component.hide);

    return () => {
      cleanup();
      floatingElement.removeEventListener('mouseenter', component.show);
      floatingElement.removeEventListener('mouseleave', component.hide);
    };
  });

  // The `{{~@x~}}` is used for whitespace control to ensure we don't insert a leading whitespace element
  <template>
    {{~@x~}}
    <span class='anchor' {{this.onInsertAnchor this}} />

    {{~#if this.visible}}
      {{#in-element this.containerElement}}
        <div class='tooltip' ...attributes {{this.attachTooltip this side=@side}}>
          {{#if (has-block)}}
            {{yield}}
          {{else}}
            {{@text}}
          {{/if}}
        </div>
      {{/in-element}}
    {{/if~}}
  </template>
}
